package com.songqi.common.redis.lock;

import com.songqi.common.core.utils.uuid.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @description:
 * @author: songqi(13776346982)
 * @date: 2023/8/21 14:17
 */
@Configuration
public class RedisDistributedLock implements DistributedLock{


    private static final Logger log = LoggerFactory.getLogger(RedisDistributedLock.class);

    /**
     * 存放当前线程获取锁后产生的唯一编号。
     * 作用：从当前线程中取出唯一编号后，传递给Lua解锁脚本，用来判断当前释放锁的线程是否与加锁的线程是同一个。
     */
    private final ThreadLocal<String> lockFlag = new ThreadLocal<>();

    private static final String UNLOCK_LUA_SCRIPT =
            "if redis.call(\"get\", KEYS[1]) == ARGV[1] " +
            "then " +
            "   return redis.call(\"del\", KEYS[1]) " +
            "else " +
            "   return 0 " +
            "end ";
    private static final RedisScript<Long> UNLOCK_REDIS_SCRIPT = new UnlockRedisScript<>(UNLOCK_LUA_SCRIPT, Long.class);


    private final RedisTemplate<String, String> redisTemplate;

    public RedisDistributedLock(RedisTemplate<String, String> redisTemplate){
        this.redisTemplate = redisTemplate;
    }

    @Override
    public boolean lock(String key) {
        return lock(key, TIMEOUT_MILLIS, RETRY_TIMES, SLEEP_MILLIS);
    }

    @Override
    public boolean lock(String key, int retryTimes) {
        return lock(key, TIMEOUT_MILLIS, retryTimes, SLEEP_MILLIS);
    }

    @Override
    public boolean lock(String key, int retryTimes, long sleepMillis) {
        return lock(key, TIMEOUT_MILLIS, retryTimes, sleepMillis);
    }

    @Override
    public boolean lock(String key, long expire) {
        return lock(key, expire, RETRY_TIMES, SLEEP_MILLIS);
    }

    @Override
    public boolean lock(String key, long expire, int retryTimes) {
        return lock(key, expire, retryTimes, SLEEP_MILLIS);
    }

    @Override
    public boolean lock(String key, long expire, int retryTimes, long sleepMillis) {
        boolean result = this.setRedis(key, expire);
        // 如果获取锁失败，按照传入的重试次数进行重试
        while ((!result) && retryTimes-- > 0){
            try {
                Thread.sleep(sleepMillis);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            result = this.setRedis(key, expire);
        }
        return result;
    }

    @Override
    public boolean releaseLock(String key) {
        try {
            List<String> keys = new ArrayList<>();
            keys.add(key);
            Long result = redisTemplate.execute(UNLOCK_REDIS_SCRIPT, keys, lockFlag.get());
            if(result != null){
                return result == 1;
            }
        } catch (Exception e){
            log.error("Release redis distributed lock occurred an exception", e);
        } finally {
            lockFlag.remove();
        }
        return false;
    }

    @Override
    public boolean unlock(String key){
        return releaseLock(key);
    }

    private boolean setRedis(String key, long expire){
        try {
            String uuid = UUID.randomUUID().toString();
            lockFlag.set(uuid);
            return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, uuid, expire, TimeUnit.MILLISECONDS));
        } catch (Exception e){

            log.error("Set redis distributed lock occurred an exception", e);
        }
        return false;

    }
}
